require "cgi"
require 'net/http'
require "simple-json"
require "MyUtil"
require "zlib"
require "stringio"

class LivedoorReaderWp
  attr_reader :api_key

  # 引数 gdgd はちょっと考える tag:todo
  def initialize(username, use_gzip=true, server="reader.livedoor.com", port="80", path="/reader/", login_server="member.livedoor.com", login_path="/login/index")
    # proxy 越しに ldr にアクセスしたい場合は↓を設定
    @PROXY_ADDR=nil
    @PROXY_PORT=nil

    # todo: user-agent どうしよう？
    @default_header =  {
      'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      'Accept-Language' => 'ja,en-us;q=0.7,en;q=0.3',
      'Accept-Charset' => 'Shift_JIS,utf-8;q=0.7,*;q=0.7',
      "User-Agent" => 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.2; .NET CLR 1.1.4322)'
    }
    @username, @use_gzip, @server, @port, @path, @login_server, @login_path =username, use_gzip, server, port, path, login_server, login_path
    if @use_gzip
      require "zlib"
      @default_header['Accept-Encoding'] = 'gzip,deflate'
    end
    @API_KEY_NAME="reader_sid"
    @count = 1
    @DEBUG = false
  end

  # デバッグ用。Net::HTTPResponse をテキトーに整形して出力
  # log.log をメインのログとして、ウザそうな物は log_%タイムスタンプ%.log に保存
  def debug_http_resp_dump(resp) 
    unless @DEBUG
      return
    end
    File.open("log.log", "a+"){|fdebug|

      fdebug.puts( ("begin_debug:[" + $count.to_s + "]") .rjust(70).gsub(" ","-") )
      space = 15
      # ステータスライン表示部
      ary = [ "code", "message", "http_version" ]
      #ary.each do |method|
      fdebug.puts "code".ljust(space) + "|" + resp.code
      fdebug.puts "message".ljust(space) + "|" + resp.message
      fdebug.puts "http_version".ljust(space) + "|" + resp.http_version
      #end
      # ヘッダ表示部
      resp.each_name do |name|
        unless name == "set-cookie"
          fdebug.puts name.ljust(space) + "|" + resp.get_fields(name).join("")
        else
          fdebug.puts name.ljust(space) + "|"
          resp.get_fields(name).each{|v|fdebug.puts "\t" + v}
        end
      end
      # body 表示部
      begin
        fdebug.puts "body:"
        if resp['Content-Length'].to_i > 1000 || resp.body.length > 1000

          sec, min, hour, mday, mon, year, wday, yday, isdst, zone = Time.now.to_a.map do |item|
            item.to_s.rjust(2,"0")
          end
          longfile = "log_" + year + mon + mday + hour + min + sec + ".log"
          File.open(longfile  , "w+b"){|f|f.print resp.body}
          fdebug.puts "ログは長いので" + longfile + "に保存したよ"
        elsif resp['Content-Encoding'] == "gzip"
          fdebug.puts "gzip 展開します"
          require "zlib"
          Zlib::GzipReader.wrap(StringIO.new(resp.body)){|z|fdebug.print z.read}
          fdebug.puts ""
        else
          fdebug.puts resp.body
        end
      rescue => e
        fdebug.puts e.to_s
      end
      fdebug.puts "end_debug:".rjust(70).gsub(" ","-")
      @count = @count + 1
    }
  end



  # @coolie を上書きして,かつ name + domain なファイル名で yaml として保存する. set-cookie が無い場合は何もしません
  def update_cookie( http_response )
    if f = http_response.get_fields('Set-Cookie')
      f.each{|str|
        k,v = str[0...str.index(';')].split('=')
        @cookie[k] = v
      }
      # これは members.livedoor.com のクッキーなので、消した方が良い？
      # @cookie.delete("mem_sid")
      # そもそも domain や path を処理すべき時が来るかもしれない？
      # 生きてる間に他のfqdnにアクセスしない限り気にする事ない？ tag:todo
      MyUtil.save_obj( @cookie, @username + "@" + @server + ".cookie" )
    end
  end

  def logged?
    begin
      @cookie = MyUtil.load_obj(@username + "@" + @server + ".cookie")
      unless @cookie[".LRC"] && @cookie[".LL"] && @cookie[".LH"] && @cookie[@API_KEY_NAME]
        return false
      end
      return true
    rescue => e
      puts e.to_s
      return false
    end
  end

  def hash2cookie( cookie )
    {"Cookie" => (cookie.to_a.map{|value|value.join("=")}).join("; ") } if cookie
  end

  def login(password)
    # @username
    @login_param = URI.encode("&livedoor_id=" + @username +
                              "&password=" + password )
    @cookie = {}
    post(@login_path, [@login_param], @login_server) #login
    unless @cookie[".LRC"] && @cookie[".LL"] && @cookie[".LH"]
      raise
    end

    get(@path) # api_key 取得
    unless @cookie[@API_KEY_NAME]
      raise
    end
    @api_key = @cookie[@API_KEY_NAME]
  end

  def get(path, param=nil, server=@server, port=@port)
    Net::HTTP.start(server, port, @PROXY_ADDR, @PROXY_PORT) do |http|
      response = http.get(path, @default_header.merge(hash2cookie(@cookie)))
      debug_http_resp_dump(response)
      update_cookie(response)
      if @use_gzip
        Zlib::GzipReader.wrap(StringIO.new(response.body)){|z|z.read}
      else
        response.body
      end
    end
  end

  def post(path, param=nil, server=@server, port=@port)
    Net::HTTP.start(server, port, $PROXY_ADDR, $PROXY_PORT) do |http|
      response = http.post(path, create_param(param), @default_header.merge(hash2cookie(@cookie)))
      debug_http_resp_dump(response)
      update_cookie(response)
      if @use_gzip
        Zlib::GzipReader.wrap(StringIO.new(response.body)){|z|z.read}
      else
        response.body
      end
    end
  end

  def perse_json(src)
    unless @cookie[".LRC"] && @cookie[".LL"] && @cookie[".LH"] && @cookie[@API_KEY_NAME]
      raise
    end
    parser = JsonParser.new
    parser.parse(src)
  end

  def post_api(path, param=nil, server=@server, port=@port)
    perse_json(post(path, param, server, port))
  end

  def create_param(str=nil)
    @api_key = @cookie[@API_KEY_NAME]
    if str and @api_key
      str << "ApiKey="+ @api_key
      str.join("&")
    elsif str
      str.join("&")
    elsif @api_key
      "ApiKey="+ @api_key
    else
      nil
    end
  end
 
# =============================================================================
# =============================================================================
# ここから下は ldr の API アクセス 
# =============================================================================
# =============================================================================
  # from_id 連続取得するときに前回最後の subscribe_id を指定？
  # -unread:0で全部, 1で未読のみ,from_id:取得開始位置,limit:取得上限
  def subs(unread=0, from_id=nil, limit=nil)
    url = '/api/subs'
    url << "?unread=" + unread.to_s
    url << "&from_id=" + from_id.to_s if from_id
    url << "&limit=" + limit.to_s if limit
    post_api(url)
  end
  # Feed の内容を取得
  # - subscribe_id,offset:開始位置,limit:件数
  def all(subscribe_id, offset, limit)
    post_api('/api/all', ["subscribe_id="+ subscribe_id.to_s ,"offset=" + offset.to_s,"limit=" + limit.to_s] )
  end
  # Feed 内容の内未読部分を返す(件数は設定に依存？)
  def unread(subscribe_id)
    post_api('/api/unread', ["subscribe_id=" + subscribe_id.to_s])
  end
  # Feed 既に読化
  def touch_all(subscribe_id)
    post_api('/api/touch_all', ["subscribe_id=" + subscribe_id.to_s])
  end
  def folders()
    post_api('/api/folders')
  end
# ==============================================================================
# config
  def config_save()
    raise "未実装"
  end
  def config_load(timestamp)
    raise "未実装"
  end
# ==============================================================================
# feed
#   def feed_discover(url)
#     post("/api/feed/discover", ["url=" + url])
#   end
#   def feed_subscribe(feedlink)
#     post("/api/feed/subscribe", ["feedlink=" + feedlink])
#   end
#   def feed_unsubscribe(subscribe_id)
#     post("/api/feed/unsubscribe", ["subscribe_id=" + subscribe_id.to_s])
#   end
#   def feed_set_rate(subscribe_id, rate)
#     post("/api/feed/set_rate", ["subscribe_id=" + subscribe_id.to_s, "rate=", rate.to_s])
#   end
#   # to:移動先フォルダ名.空の文字列で未分類へ
#   def feed_move(subscribe_id, to)
#     post("/api/feed/move", ["subscribe_id=" + subscribe_id.to_s, "to=", to.to_s])
#   end
# ==============================================================================
# folder
#  def folder_create(name)
#    post("/api/folder/create", ["name=" + name])
#  end
#  def folder_delete(name)
#    post("/api/folder/delete", ["name=" + name])
#  end
# ==============================================================================
# pin
#  def pin_all
#    post("/api/pin/all")
#  end
  def pin_add(link, title)
    post("/api/pin/add", ["link=" + link, "title=" + title])
  end
#  def pin_remove
#    post("/api/pin/remove", ["link="+link])
#  end
#  def pin_clear
#    post("/api/pin/clear")
#  end
end
